/*******************************************************************************
 * Copyright (c) 2010 Oak Ridge National Laboratory.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 * 
 * SPDX-License-Identifier: EPL-2.0
 ******************************************************************************/
package org.eclipse.nebula.visualization.xygraph.dataprovider;

import java.util.AbstractCollection;
import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * A particular circular buffer. New arrived data will be appended to the tail
 * of the buffer. When buffer is full, the oldest data will be deleted when new
 * data arrived.
 * 
 * @author Xihui Chen
 */
public class CircularBuffer<T> extends AbstractCollection<T> {
	private int bufferSize = 0;
	private T[] buffer;
	private int head;
	private int tail;
	private int count;

	public CircularBuffer(int bufferSize) {
		if (bufferSize <= 0)
			throw new IllegalArgumentException("Buffer size must be greater than zero.");
		this.setBufferSize(bufferSize, true);
	}

	/**
	 * Add an element.
	 * 
	 * @param element
	 */
	public synchronized boolean add(T element) {
		if (tail == head && count == bufferSize) { // buffer is full
			buffer[tail] = element;
			head = (head + 1) % bufferSize;
			tail = (tail + 1) % bufferSize;
			return true;
		} else {// buffer is not full
			buffer[tail] = element;
			tail = (tail + 1) % bufferSize;
			count++;
			return true;
		}
	}

	/**
	 * Get element
	 * 
	 * @param index
	 *            the index of the element in the buffer.
	 * @return the element. null if the data at the index doesn't exist.
	 */
	public synchronized T getElement(int index) {
		if (index < count)
			return buffer[(head + index) % bufferSize];
		else
			return null;
	}

	/**
	 * Get head element
	 * 
	 * @return the head element. null if the buffer is empty.
	 */
	public synchronized T getHead() {
		if (count > 0)
			return buffer[head];
		else
			return null;
	}

	/**
	 * Get tail element
	 * 
	 * @return the tail element. null if the buffer is empty.
	 */
	public synchronized T getTail() {
		if (count > 0)
			return buffer[(head + count - 1) % bufferSize];
		else
			return null;
	}

	/**
	 * clear the buffer;
	 */
	public synchronized void clear() {
		for (int i = 0; i < count; i++) {
			buffer[(head + i) % bufferSize] = null;
		}
		head = 0;
		tail = 0;
		count = 0;
	}

	/**
	 * Set the buffer size.
	 * 
	 * @param bufferSize
	 *            the bufferSize to set
	 * @param clear
	 *            clear the buffer if true. Otherwise keep the exist data;
	 *            Oldest data will be omitted if the new bufferSize is less than
	 *            the exist data count.
	 */
	@SuppressWarnings("unchecked")
	public synchronized void setBufferSize(int bufferSize, boolean clear) {
		assert bufferSize > 0;
		if (this.bufferSize != bufferSize) {
			if (clear) {// clear
				buffer = (T[]) new Object[bufferSize];
				clear();
			} else {// keep the exist data
				T[] tempBuffer = (T[]) toArray();
				buffer = (T[]) new Object[bufferSize];
				if (bufferSize < count) {
					System.arraycopy(tempBuffer, count - bufferSize, buffer, 0, bufferSize);
				} else {
					System.arraycopy(tempBuffer, 0, buffer, 0, count);
				}
				count = Math.min(bufferSize, count);
				head = 0;
				tail = count % bufferSize;
			}
			this.bufferSize = bufferSize;
		}
	}

	/**
	 * @return the bufferSize
	 */
	public synchronized int getBufferSize() {
		return bufferSize;
	}

	public Iterator<T> iterator() {
		return new Iterator<T>() {
			private int index = 0;

			public boolean hasNext() {
				return index < count;
			}

			public T next() {
				if (!hasNext())
					throw new NoSuchElementException();
				return buffer[(head + index++) % bufferSize];
			}

			public void remove() {
				throw new UnsupportedOperationException("Remove is not supported.");
			}
		};
	}

	@Override
	public int size() {
		return count;
	}

}
